﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Diagnostics;
using System.Linq;


namespace SStuff.FlowControls
{
    /// <summary>
    /// Base class of the FlowItemsControl.
    /// Handles the common behavior between 2D and 3D mode (LayoutOffset calculation, manipulation / inertia etc.)
    /// </summary>
    [TemplatePart(Type = typeof(ItemsPresenter), Name = "ItemsPresenter")]
    [TemplateVisualState(GroupName = "Manipulation", Name = "Stabilized")]
    [TemplateVisualState(GroupName = "Manipulation", Name = "Manipulating")]
    [TemplateVisualState(GroupName = "Manipulation", Name = "Inertia")]
    public abstract class FlowItemsControlBase : ItemsControl
    {
        bool _stopSelectionEventPropagation;
        private bool _selectedIndexNeeded;
        private bool _selectedItemNeeded;


        Storyboard _layoutStoryBoard;
        DoubleAnimation _layoutOffsetAnim;
        bool _loaded;
        /// <summary>
        /// Initialize the instance
        /// </summary>
        protected FlowItemsControlBase()
        {
           

            LayoutUpdated += OnLayoutUpdated;
            IsTabStop = true;

            Loaded += (s, a) =>
               {
                   _loaded = true;
                   //Handle the Rendering event for inertia based layout
                   InvalidateMeasure();
                   HandleRenderingIfNeeded();
               };
            
//#if PHONE
//            LayoutUpdated += (s, a) =>
//                {
//                    if (!this.IsInMainVisualTree())
//                    {
//                        if (_loaded)
//                        {
//                            // unhandle the Rendering event
//                            if (_layoutStoryBoard != null)
//                                _layoutStoryBoard.Pause();
//                            CompositionTarget.Rendering -= OnRendering;
//                            _loaded = false;
//                        }
//                    }
//                    else
//                    {
//                        if (!_loaded)
//                        {
//                            _loaded = true;
//                            //Handle the Rendering event for inertia based layout
//                            InvalidateMeasure();
//                            CompositionTarget.Rendering += OnRendering;
//                        }
//                    }
//                };
//#else
            
            Unloaded += (s, a) =>
                {
                    // unhandle the Rendering event
                    if (_layoutStoryBoard != null)
                        _layoutStoryBoard.Stop();      
                    
                    _loaded = false;
                    EnsureRenderingUnHandled();
                    Debug.WriteLine("FlowItemsControl unloaded");
                };

//#endif
        }

        protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
        {
            e.Handled = true;
        }
        protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
        {
            e.Handled = true;
        }
        protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e)
        {
            e.Handled = true;
        }

        bool _renderingHandled;

        private void HandleRenderingIfNeeded()
        {
            if (!_loaded ||_renderingHandled|| ManipulationState != FlowControls.ManipulationState.Inertia)
                return;
            _renderingHandled = true;
            CompositionTarget.Rendering += OnRendering;
        }

        private void EnsureRenderingUnHandled()
        {
            if (!_renderingHandled)
                return;
            _renderingHandled = false;
            CompositionTarget.Rendering -= OnRendering;
        }


        /// <summary>
        /// If the current ManipulationState is Inertia, compute the LayoutOffset animation
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnRendering(object sender, EventArgs e)
        {
            if (ManipulationState == ManipulationState.Inertia)
            {
                // If the current velocity is less than the predifined minimum velocity, set the manipulation state to completion
                if (Math.Abs(_currentManipulationData.Velocity) < MinVelocity)
                {
                    OnManipulationCompleted();
                    return;
                }

                // if the rendering is too fast for accurate velocity calculation, skip a frame
                var now = DateTime.Now;
                if (now == _lastManipulationData.Date)
                    return;

                // swap current and last manipulation data structures
                var current = _lastManipulationData;
                _lastManipulationData = _currentManipulationData;
                _currentManipulationData = current;

                // update the current manipulation data based on last manipulation data and the attenuation ratio
                var dt = (now - _lastManipulationData.Date).Ticks;
                var velocity = _lastManipulationData.Velocity * AttenuationRatio;
                var value = _lastManipulationData.Value + velocity * dt;
                _currentManipulationData.Date = now;
                _currentManipulationData.Value = value;
                _currentManipulationData.Velocity = velocity;

                // if the layout is in a no cyclic mode and we hit a limit, invert the velocity and apply a strong attenuation ratio to simulate a 
                // bounce
                if (!CyclicMode)
                {
                    if (LayoutOffset < 0 && _currentManipulationData.Velocity > 0)
                        _currentManipulationData.Velocity = -0.3 * _currentManipulationData.Velocity;
                    else if (LayoutOffset > Items.Count - 1 && _currentManipulationData.Velocity < 0)
                        _currentManipulationData.Velocity = -0.3 * _currentManipulationData.Velocity;

                }
                else
                {
                    // if the LayoutOffset is no more in range [0-ItemsCount], set it back at a correct value
                    var rounded = Math.Round(LayoutOffset);
                    if (rounded < 0)
                    {
                        LayoutOffset += Items.Count;

                    }
                    else if (rounded > Items.Count - 1)
                    {
                        LayoutOffset -= Items.Count;
                    }
                }
                // Apply the delta between last and current manipulation data to the layout offset
                var delta = _currentManipulationData.Value - _lastManipulationData.Value;
                LayoutOffset -= delta * 0.5 * GetConsideredItems();


            }
        }

        /// <summary>
        /// Handle the LayoutUpdated event until it gets a connection to the ItemsHost (the FlowPanel)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void OnLayoutUpdated(object sender, EventArgs args)
        {
            var host = ItemsHost;
            if (host != null)
            {
                //#if !PHONE
                VirtualHost = host;
                //#else
                //                VirtualHost = host;
                //#endif
                LayoutUpdated -= OnLayoutUpdated;
            }
            else
            {
                Dispatcher.BeginInvoke(()=>InvalidateMeasure());
            }
        }

        /// <summary>
        /// Determines if the specified element is a descendant of the control in the visual tree
        /// </summary>
        /// <param name="element">element to test</param>
        /// <returns></returns>
        public bool IsWithin(DependencyObject element)
        {
            var candidate = element;
            while (candidate != null)
            {
                if (candidate == this)
                    return true;
                candidate = VisualTreeHelper.GetParent(candidate);
            }
            return false;
        }


        /// <summary>
        /// Get or set the current manipulation state
        /// </summary>
        public ManipulationState ManipulationState
        {
            get { return (ManipulationState)GetValue(ManipulationStateProperty); }
            set { SetValue(ManipulationStateProperty, value); }
        }

        /// <summary>
        /// Dependency property for ManipulationState
        /// </summary>
        public static readonly DependencyProperty ManipulationStateProperty =
            DependencyProperty.Register("ManipulationState", typeof(ManipulationState), typeof(FlowItemsControlBase), new PropertyMetadata(ManipulationState.Stabilized,
                (s, a) => ((FlowItemsControlBase) s).OnManipulationStateChanged()));

        /// <summary>
        /// Update the current visual state for ManipulationState
        /// </summary>
        private void OnManipulationStateChanged()
        {
            if (ManipulationState == FlowControls.ManipulationState.Inertia)
                HandleRenderingIfNeeded();
            else
                EnsureRenderingUnHandled();
            VisualStateManager.GoToState(this, ManipulationState.ToString(), true);
        }

        /// <summary>
        /// Handles the beginning of a manipulation
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            var originalSource = e.OriginalSource as DependencyObject;
            if (originalSource.GetVisualAncestors().OfType<FlowItemBase>().Any())
                return;
            //base.OnMouseLeftButtonDown(e);
            e.Handled = true;
#if SILVERLIGHT
            var focusedElement = FocusManager.GetFocusedElement() as DependencyObject;
            if (focusedElement == null)
            {
                Focus();
            }
            else
            {
                if (!IsWithin(focusedElement))
                    Focus();
            }
#else
            var focusedElement = FocusManager.GetFocusedElement(this) as DependencyObject;
            if (focusedElement == null)
            {
                Focus();
            }
            else
            {
                if (!IsWithin(focusedElement))
                    Focus();
            }
#endif

            if (HandleGestures && CaptureMouse())
            {
                OnManipulationStart(e.GetPosition(this));
            }
        }


        /// <summary>
        /// If the current state is "Manipulating", continue the manipulation
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            if (ManipulationState == ManipulationState.Manipulating)
            {
                OnManipulationDelta(e.GetPosition(this));
            }
        }


        /// <summary>
        /// Pass from the "Manipulating" state to "Inertia"
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            //base.OnMouseLeftButtonUp(e);
            e.Handled = true;
            ReleaseMouseCapture();
            if (ManipulationState == ManipulationState.Manipulating)
                OnManipulationEnd();
        }

        /// <summary>
        /// Class containing data for a manipulation step
        /// </summary>
        private class ManipulationData
        {

            public DateTime Date { get; set; }
            public double Value { get; set; }
            public double Velocity
            {
                get;
                set;
            }

            public void CopyTo(ManipulationData lastManipulationData)
            {
                lastManipulationData.Date = Date;
                lastManipulationData.Value = Value;
                lastManipulationData.Velocity = Velocity;
            }
        }

        private ManipulationData _currentManipulationData = new ManipulationData();
        private ManipulationData _lastManipulationData = new ManipulationData();

        /// <summary>
        /// Initialise the beginning of a manipulation
        /// </summary>
        /// <param name="mousePosition"></param>
        private void OnManipulationStart(Point mousePosition)
        {
            if (Items.Count == 0)
            {
                ManipulationState = ManipulationState.Stabilized;
                return;
            }
            if (_layoutStoryBoard != null)
            {
                var current = LayoutOffset;
                _layoutStoryBoard.Stop();
                LayoutOffset = current;
            }
            ManipulationState = ManipulationState.Manipulating;
            _currentManipulationData.Date = DateTime.Now;
            _currentManipulationData.Velocity = 0;
            _currentManipulationData.Value = GetNormalizedGestureOffset(mousePosition);
            _currentManipulationData.CopyTo(_lastManipulationData);
        }

        /// <summary>
        /// Get the normalized offset [-1 - +1] for a given mouse position
        /// </summary>
        /// <param name="mousePosition"></param>
        /// <returns></returns>
        protected abstract double GetNormalizedGestureOffset(Point mousePosition);

        /// <summary>
        /// Get the number of items to consider for layouting (used for virtualization purpose)
        /// </summary>
        /// <returns></returns>
        protected abstract int GetConsideredItems();

        /// <summary>
        /// Minimum velocity constant
        /// </summary>
        private const double MinVelocity = 0.00000001;
        /// <summary>
        /// Attenuation ratio constant
        /// </summary>
        private const double AttenuationRatio = 0.94;

        /// <summary>
        /// Handles a Manipulating step
        /// </summary>
        /// <param name="mousePosition"></param>
        private void OnManipulationDelta(Point mousePosition)
        {
            if (Items.Count == 0)
            {
                ManipulationState = ManipulationState.Stabilized;
                return;
            }
            var now = DateTime.Now;
            if (now != _currentManipulationData.Date)
            {
                var current = _lastManipulationData;
                _lastManipulationData = _currentManipulationData;
                _currentManipulationData = current;
                _currentManipulationData.Date = now;
                _currentManipulationData.Value = GetNormalizedGestureOffset(mousePosition);
                _currentManipulationData.Velocity = (_currentManipulationData.Value - _lastManipulationData.Value) /
                                                    (_currentManipulationData.Date - _lastManipulationData.Date).Ticks;
                var delta = _currentManipulationData.Value - _lastManipulationData.Value;

                var candidate = LayoutOffset - delta * 0.5 * GetConsideredItems();
                if (CyclicMode)
                {
                    LayoutOffset = candidate;
                }
                else
                {
                    if (candidate < 0)
                        candidate = 0;
                    else if (candidate > Items.Count - 1)
                        candidate = Items.Count - 1;
                    LayoutOffset = candidate;
                }
            }
            else
            {

                var delta = GetNormalizedGestureOffset(mousePosition) - _currentManipulationData.Value;
                var candidate = LayoutOffset - delta * 0.5 * GetConsideredItems();
                if (CyclicMode)
                {
                    LayoutOffset = candidate;
                }
                else
                {
                    if (candidate < 0)
                        candidate = 0;
                    else if (candidate >= Items.Count)
                        candidate = Items.Count - 1;
                    LayoutOffset = candidate;
                }
            }
        }

        /// <summary>
        /// Handle the end of a manipulation (and the beginning of an inertia step)
        /// </summary>
        private void OnManipulationEnd()
        {

            _currentManipulationData.Date = DateTime.Now;
            _currentManipulationData.Velocity = (_currentManipulationData.Value - _lastManipulationData.Value) /
                                                (_currentManipulationData.Date - _lastManipulationData.Date).Ticks;
            ManipulationState = ManipulationState.Inertia;
        }
        /// <summary>
        /// When the manipulation + inertia is completed, the FlowItemsControl stabilizes itself to the closer item
        /// </summary>
        private void OnManipulationCompleted()
        {
            if (Items.Count == 0)
            {
                ManipulationState = ManipulationState.Stabilized;
                return;
            }

            while (Math.Round(LayoutOffset) < 0)
            {
                LayoutOffset += Items.Count;

            }
            while (Math.Round(LayoutOffset) >= Items.Count)
            {
                LayoutOffset -= Items.Count;
            }
            SelectedIndex = (int)Math.Round(LayoutOffset);
            AnimateLayoutOffset(SelectedIndex);
            ManipulationState = ManipulationState.Stabilized;
        }
        /// <summary>
        /// When mouse capture is lost we do the same as a mouse up event
        /// </summary>
        /// <param name="e"></param>
        protected override void OnLostMouseCapture(MouseEventArgs e)
        {
            base.OnLostMouseCapture(e);
            if (ManipulationState == ManipulationState.Manipulating)
                OnManipulationEnd();
        }

        /// <summary>
        /// Get a value indicating if gestures should be handled
        /// </summary>
        protected abstract bool HandleGestures { get; }


        Panel _itemsHost;

        /// <summary>
        /// Get the current ItemsHost (FlowPanel)
        /// </summary>
        private Panel ItemsHost
        {
            get
            {
                // Lookup the ItemsHost if we haven't already cached it.
                if (_itemsHost == null && ItemContainerGenerator != null)
                {
                    //this.GetVisualAncestors
                    //// Get any live container
                    //DependencyObject container = ItemContainerGenerator.ContainerFromIndex(0);
                    //if (container != null)
                    //{
                    //    // Get the parent of the container
                    //    _itemsHost = VisualTreeHelper.GetParent(container) as Panel;
                    //    if (_itemsHost != null) _itemsHost.InvalidateMeasure();
                    //}
                    _itemsHost = this.GetVisualDescendants().OfType<Panel>().Where(p=>p is FlowPanelBase || p is NotVirtualizedFlowPanelBase).FirstOrDefault();
                    
                }

                return _itemsHost;
            }
        }

        private void ResetLayoutOffsetAnim(double from)
        {
            if (double.IsNaN(from))
                from = 0;
            if (_layoutStoryBoard != null)
            {
                _layoutStoryBoard.Stop();
                _layoutStoryBoard.Children.Clear();
            }
            _layoutOffsetAnim = new DoubleAnimation { EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut }, Duration = new Duration(TimeSpan.FromMilliseconds(500)), From = from };
            Storyboard.SetTarget(_layoutOffsetAnim, this);
            Storyboard.SetTargetProperty(_layoutOffsetAnim, new PropertyPath(LayoutOffsetProperty));
            _layoutStoryBoard = new Storyboard { Children = { _layoutOffsetAnim } };
        }

        /// <summary>
        /// Animate the Layout offset to the specified value
        /// </summary>
        /// <param name="to"></param>
        private void AnimateLayoutOffset(double to)
        {

            double layoutOffset = LayoutOffset;
            if (CyclicMode)
            {
                if (layoutOffset + Items.Count - to < to - LayoutOffset)
                {
                    layoutOffset += Items.Count;
                }
                else if (to + Items.Count - LayoutOffset < LayoutOffset - to)
                {
                    layoutOffset -= Items.Count;
                }
//#if SILVERLIGHT
//                LayoutOffset = layoutOffset;
//#else
//                _layoutStoryBoard.Stop();
//                _layoutOffsetAnim.From = layoutOffset;
//#endif
                
            }

            if (double.IsNaN(layoutOffset))
                layoutOffset = 0;
            ResetLayoutOffsetAnim(layoutOffset);

            if (!_imediatelyLayoutChanges)
            {
                TimeSpan perItemBasedDuration = TimeSpan.FromMilliseconds(AnimationDurationPerItem.TotalMilliseconds * Math.Abs(to - layoutOffset));
                _layoutOffsetAnim.Duration = new Duration(perItemBasedDuration > MinimumAnimationDuration ? perItemBasedDuration : MinimumAnimationDuration);
            }
            else
            {
                _layoutOffsetAnim.Duration = new Duration(TimeSpan.Zero);
            }
            _layoutOffsetAnim.To = to;
            _layoutStoryBoard.Begin();
        }

        /// <summary>
        /// Clear a FlowItem
        /// </summary>
        /// <param name="element"></param>
        /// <param name="item"></param>
        protected override void ClearContainerForItemOverride(DependencyObject element, object item)
        {
            base.ClearContainerForItemOverride(element, item);
            var asFlowItemBase = element as FlowItemBase;
            if (asFlowItemBase != null)
            {
                asFlowItemBase.Content = null;
                asFlowItemBase.IsSelected = false;
            }
        }

        /// <summary>
        /// Bind a FlowItem to its content
        /// </summary>
        /// <param name="element"></param>
        /// <param name="item"></param>
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);
            var asItem = element as FlowItemBase;
            if (asItem != null)
            {
                asItem.Owner = this;
                if (SelectedItem == item)
                    asItem.IsSelected = true;
            }

        }

        bool _imediatelyLayoutChanges;

        /// <summary>
        /// Handles changes in the collection of items
        /// </summary>
        /// <param name="e"></param>
        protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            base.OnItemsChanged(e);
            if (_selectedIndexNeeded)
            {
                if (Items.Contains(SelectedItem))
                {
                    SelectedIndex = Items.IndexOf(SelectedItem);
                }
            }
            else if (_selectedItemNeeded)
            {
                if (Items.Count > SelectedIndex)
                {
                    SelectedItem = Items[SelectedIndex];
                }
            }
            else if (!Items.Contains(SelectedItem))
                SelectedItem = null;

            _selectedIndexNeeded = false;
            _selectedItemNeeded = false;

            InvalidateMeasure();

            _imediatelyLayoutChanges = true;
            
            if (SelectedItem != null)
            {

                if (Items.Contains(SelectedItem))
                {
                    SelectedIndex = Items.IndexOf(SelectedItem);
                }
                else
                {
                    SelectedItem = null;
                    SelectedIndex = 0;
                }
            }
            else
            {
                SelectedIndex = 0;                
            }

            _imediatelyLayoutChanges = false;

        }

        /// <summary>
        /// Handles Selection changes
        /// </summary>
        protected virtual void OnSelectedItemChanged()
        {
            if (_stopSelectionEventPropagation)
                return;

            _stopSelectionEventPropagation = true;

            if (SelectedIndex >= 0)
            {
                var selectedContainer = ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as FlowItemBase;
                if (selectedContainer != null)
                {
                    selectedContainer.IsSelected = false;
                }
            }

            if (SelectedItem == null)
            {
                SelectedIndex = -1;
            }
            else
            {
                if (Items.Contains(SelectedItem))
                {
                    SelectedIndex = Items.IndexOf(SelectedItem);
                    AnimateLayoutOffset(SelectedIndex);
                    var container = ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as FlowItemBase;
                    if (container != null)
                    {
                        container.IsSelected = true;
                    }
                }
                else
                {
                    // wait for the ItemsSource to be fetched
                    _selectedIndexNeeded = true;
                }
            }

            ManipulationState = ManipulationState.Stabilized;
            _stopSelectionEventPropagation = false;
        }

        /// <summary>
        /// Handles selection changes
        /// </summary>
        protected virtual void OnSelectedIndexChanged()
        {
            if (_stopSelectionEventPropagation)
                return;

            _stopSelectionEventPropagation = true;
            if (SelectedItem != null)
            {
                var selectedContainer = ItemContainerGenerator.ContainerFromItem(SelectedItem) as FlowItemBase;
                if (selectedContainer != null)
                {
                    selectedContainer.IsSelected = false;
                }
            }
            if (SelectedIndex == -1)
            {
                SelectedItem = null;
            }
            else
            {
                if (Items.Count > SelectedIndex)
                {
                    SelectedItem = Items[SelectedIndex];
                    var container = ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as FlowItemBase;
                    if (container != null)
                    {
                        container.IsSelected = true;
                    }
                }
                else
                {
                    // wait for the ItemsSource to be fetched
                    _selectedItemNeeded = true;
                }
            }

            _stopSelectionEventPropagation = false;

            ManipulationState = ManipulationState.Stabilized;
            AnimateLayoutOffset(SelectedIndex == -1 ? 0.0 : SelectedIndex);
        }



        protected virtual void OnVirtualHostChanged()
        {
        }



        protected virtual void OnLayoutOffsetChanged()
        {
            
        }

        protected virtual void OnCyclicModeChanged()
        {
        }


        /// <summary>
        /// Get or set the cyclic mode
        /// </summary>
        public bool CyclicMode
        {
            get { return (bool)GetValue(CyclicModeProperty); }
            set { SetValue(CyclicModeProperty, value); }
        }

        /// <summary>
        /// DependencyProperty for CyclicMode
        /// </summary>
        public static readonly DependencyProperty CyclicModeProperty =
            DependencyProperty.Register("CyclicMode", typeof(bool), typeof(FlowItemsControlBase), new PropertyMetadata(true, (s, a) => ((FlowItemsControlBase) s).OnCyclicModeChanged()));





        /// <summary>
        /// Get or set the minimum duration of a layout animation
        /// </summary>
        public TimeSpan MinimumAnimationDuration
        {
            get { return (TimeSpan)GetValue(MinimumAnimationDurationProperty); }
            set { SetValue(MinimumAnimationDurationProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MinimumAnimationDuration.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinimumAnimationDurationProperty =
            DependencyProperty.Register("MinimumAnimationDuration", typeof(TimeSpan), typeof(FlowItemsControlBase), new PropertyMetadata(TimeSpan.FromMilliseconds(1000)));


        /// <summary>
        /// Get or set the animation duration per item
        /// </summary>
        public TimeSpan AnimationDurationPerItem
        {
            get { return (TimeSpan)GetValue(AnimationDurationPerItemProperty); }
            set { SetValue(AnimationDurationPerItemProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AnimationDurationPerItem.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AnimationDurationPerItemProperty =
            DependencyProperty.Register("AnimationDurationPerItem", typeof(TimeSpan), typeof(FlowItemsControlBase), new PropertyMetadata(TimeSpan.FromMilliseconds(400)));

//#if !PHONE
        /// <summary>
        /// Get the ItemsHost as a Virtualizing panel
        /// </summary>
        protected Panel VirtualHost
        {
            get { return (Panel)GetValue(VirtualHostProperty); }
            private set { SetValue(VirtualHostProperty, value); }
        }

        // Using a DependencyProperty as the backing store for VirtualHost.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty VirtualHostProperty =
            DependencyProperty.Register("VirtualHost", typeof(Panel), typeof(FlowItemsControlBase), new PropertyMetadata(null,
                (s, a) => ((FlowItemsControlBase) s).OnVirtualHostChanged()));

//#else
//        /// <summary>
//        /// Get the ItemsHost as a  panel
//        /// </summary>
//        protected Panel VirtualHost
//        {
//            get { return (Panel)GetValue(VirtualHostProperty); }
//            private set { SetValue(VirtualHostProperty, value); }
//        }

//        // Using a DependencyProperty as the backing store for VirtualHost.  This enables animation, styling, binding, etc...
//        public static readonly DependencyProperty VirtualHostProperty =
//            DependencyProperty.Register("VirtualHost", typeof(Panel), typeof(FlowItemsControlBase), new PropertyMetadata(null,
//                (s, a) => ((FlowItemsControlBase)s).OnVirtualHostChanged()));
//#endif
        /// <summary>
        /// Get or set the SelectedItem
        /// </summary>
        public object SelectedItem
        {
            get { return GetValue(SelectedItemProperty); }
            set { SetValue(SelectedItemProperty, value); }
        }

        /// <summary>
        /// DependencyProperty for SelectedItem
        /// </summary>
        public static readonly DependencyProperty SelectedItemProperty =
            DependencyProperty.Register("SelectedItem", typeof(object), typeof(FlowItemsControlBase), new PropertyMetadata(null,
                (s, a) => ((FlowItemsControlBase) s).OnSelectedItemChanged()));

        /// <summary>
        /// Get or set the Selected Index
        /// </summary>
        public int SelectedIndex
        {
            get { return (int)GetValue(SelectedIndexProperty); }
            set { SetValue(SelectedIndexProperty, value); }
        }

        /// <summary>
        /// DependencProperty for SelectedIndex
        /// </summary>
        public static readonly DependencyProperty SelectedIndexProperty =
            DependencyProperty.Register("SelectedIndex", typeof(int), typeof(FlowItemsControlBase), new PropertyMetadata(-1,
                (s, a) => ((FlowItemsControlBase) s).OnSelectedIndexChanged()));


        /// <summary>
        /// Get or set the current LayoutOffset
        /// </summary>
        public double LayoutOffset
        {
            get { return (double)GetValue(LayoutOffsetProperty); }
            set { SetValue(LayoutOffsetProperty, value); }
        }

        /// <summary>
        /// DependencyProperty for LayoutOffset
        /// </summary>
        public static readonly DependencyProperty LayoutOffsetProperty =
            DependencyProperty.Register("LayoutOffset", typeof(double), typeof(FlowItemsControlBase), new PropertyMetadata(0.0,
                (s, a) => ((FlowItemsControlBase) s).OnLayoutOffsetChanged()));



        /// <summary>
        /// Get or set the KeyboardDirection for Keyboard based navigation
        /// </summary>
        public KeyboardDirection KeyboardDirection
        {
            get { return (KeyboardDirection)GetValue(KeyboardDirectionProperty); }
            set { SetValue(KeyboardDirectionProperty, value); }
        }

        /// <summary>
        /// DependencyProperty for KeyboardDirection
        /// </summary>
        public static readonly DependencyProperty KeyboardDirectionProperty =
            DependencyProperty.Register("KeyboardDirection", typeof(KeyboardDirection), typeof(FlowItemsControlBase), new PropertyMetadata(KeyboardDirection.LeftRight));


        /// <summary>
        /// Handles keyboard events
        /// </summary>
        /// <param name="e"></param>
        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);
            if (!e.Handled)
            {
                switch (e.Key)
                {
                    case Key.Left:
                        {
                            if (KeyboardDirection.Contains(KeyboardDirection.LeftRight))
                            {
                                e.Handled = true;
                                SelectPrevious();
                            }
                        }
                        break;
                    case Key.Right:
                        {
                            if (KeyboardDirection.Contains(KeyboardDirection.LeftRight))
                            {
                                e.Handled = true;
                                SelectNext();
                            }
                        }
                        break;
                    case Key.Up:
                        {
                            if (KeyboardDirection.Contains(KeyboardDirection.UpDown))
                            {
                                e.Handled = true;
                                SelectPrevious();
                            }
                        }
                        break;
                    case Key.Down:
                        {
                            if (KeyboardDirection.Contains(KeyboardDirection.UpDown))
                            {
                                e.Handled = true;
                                SelectNext();
                            }
                        }
                        break;
                }
            }
        }

        /// <summary>
        /// Select the next item
        /// </summary>
        private void SelectNext()
        {
            if (SelectedIndex == Items.Count - 1)
            {
                if (CyclicMode)
                    SelectedIndex = 0;
            }
            else
                SelectedIndex++;
        }

        /// <summary>
        /// Select the previous item
        /// </summary>
        private void SelectPrevious()
        {
            if (SelectedIndex == 0)
            {
                if (CyclicMode)
                    SelectedIndex = Items.Count - 1;
            }
            else
                SelectedIndex--;
        }




    }
}
